/*
 * File: ExportNeo4J.java 
 *  
 * Created on 23.01.2011
 */
package de.graph.impex;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.Evaluators;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.kernel.Traversal;
import org.neo4j.kernel.Uniqueness;

import de.graph.common.Edge;
import de.graph.common.Graph;
import de.graph.common.Vertex;
import de.graph.common.impl.GraphFactory;
import de.graph.common.impl.GraphFactory.GraphType;


public class Neo4JImporter extends AbstractImportExport implements Importer {
    
    private final GraphDatabaseService databaseService;
    
    public Neo4JImporter(String dbPath) {
        super(dbPath);
        
        // FIXME - use EmbeddedReadOnlyGraphDatabase if [Neo tickets] #306 fixed
        Map<String, String> storeProperties = new HashMap<String, String>();
        storeProperties.put("neostore.nodestore.db.mapped_memory", "10M"); // nr_of_nodes * 9 bytes        
        storeProperties.put("neostore.relationshipstore.db.mapped_memory", "40M"); // nr_of_relationships * 33 bytes.
        storeProperties.put("neostore.propertystore.db.mapped_memory", "100M");
        storeProperties.put("neostore.propertystore.db.strings.mapped_memory", "300M");
        storeProperties.put("neostore.propertystore.db.arrays.mapped_memory", "0M");

        this.databaseService = new EmbeddedGraphDatabase(dbPath, storeProperties);
    }

    public Graph importGraph() {
        
        Graph graph = null;
        try {
            Node graphNode = getGraphNode();            
            if (graphNode == null) {
                throw new IllegalArgumentException("Unknow database format!");
            }
            
            boolean isDirected = false;
            if (graphNode.hasProperty("IsDirected")) {
                isDirected = (Boolean) graphNode.getProperty("IsDirected");
            }
            
            if (isDirected) {
                graph = GraphFactory.create(GraphType.DIRECTED);
            } 
            else {
                graph = GraphFactory.create(GraphType.UNDIRECTED);
            }

            // note: for 430.000 V, 950.000 E 
            //         traversal            - 0:20 
            //         access to properties - 3:30 
            Map<Long, Vertex> neoIdVertexMap = new HashMap<Long, Vertex>();

            TraversalDescription description = Traversal.description();
            description = description.depthFirst();
            description = description.expand(Traversal.expanderForAllTypes(Direction.OUTGOING));
            description = description.evaluator(Evaluators.all());
            description = description.uniqueness(Uniqueness.RELATIONSHIP_GLOBAL);

            Iterable<Path> paths = description.traverse(databaseService.getReferenceNode());
            for (Path path : paths) {

                Relationship lastRelationship = path.lastRelationship();
                if (lastRelationship != null) {
                    Vertex source = createVertex(graph, lastRelationship.getStartNode(), neoIdVertexMap);
                    Vertex target = createVertex(graph, lastRelationship.getEndNode(), neoIdVertexMap);

                    createEdge(graph, source, target, lastRelationship);
                }
            }
        }
        finally {
            shutdown();
        }
        
        return graph;
    }

    @Override
    protected void shutdown() {
        databaseService.shutdown();
    }

    private Node getGraphNode() {
        
        // scan for graph node            
        Node graphNode = null;
        for (Node node : databaseService.getAllNodes()) {
            if (node.hasProperty("NodeType") && Graph.class.getName().equals(node.getProperty("NodeType"))) {
                graphNode = node;
                
                break;
            }
        }
        
        return graphNode;
    }
    
    private Vertex createVertex(Graph graph, Node node, Map<Long, Vertex> neoIdVertexMap) {
        
        Vertex vertex = neoIdVertexMap.get(node.getId());
        if (vertex == null) {
            vertex = createVertex(graph, node);
            
            neoIdVertexMap.put(node.getId(), vertex);
        }
        
        return vertex;
    }

    private Vertex createVertex(Graph graph, Node node) {
        
        Vertex vertex = graph.createVertex();         
        for (String propertyKey : node.getPropertyKeys()) {
            Object propertyValue = node.getProperty(propertyKey);
            
            vertex.setProperty(propertyKey, propertyValue);
        }
        
        // remove internal keys
        vertex.removeProperty("NodeType");
        
        // TODO
        if (vertex.getLabel().length() == 0) {
            vertex.setProperty("label", "root");
        }
        
        return vertex;
    }

    private Edge createEdge(Graph graph, Vertex source, Vertex target, PropertyContainer relationship) {
        
        Edge edge = graph.createEdge(source, target);        
        for (String propertyKey : relationship.getPropertyKeys()) {
            Object propertyValue = relationship.getProperty(propertyKey);
            
            edge.setProperty(propertyKey, propertyValue);
        }
        
        // remove internal keys
        edge.removeProperty("NodeType");
        
        return edge;
    }
}
